import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*; 

public class CBC extends Mode
{
	public CBC() {}
	
	public byte[] Encrypt(SecretKey key1, SecretKey key2, byte[] IV, byte[] plainText) throws Exception
	{
		if (IV.length != 8) 
			throw new Exception("CBC Encryption: Initalization Vector size =! 8");
	
		int textLength = plainText.length;
		int r = textLength % 8;
		
		int cipherLength;
		if (r == 0)
			cipherLength = textLength;
		else
			cipherLength = textLength + (8 - r);
		
		if (r != 0)
			plainText = Arrays.copyOf(plainText, cipherLength);
		
		byte[] cipherText = new byte[cipherLength];
		byte[] currentBlock = new byte[8];
		
		TripleDES des = new TripleDES();
		
		for (int i = 0; i < cipherLength; i += 8)
		{
			if (i == 0)
				currentBlock = super.XOR(Arrays.copyOfRange(plainText, i, i + 8), IV);
			else
				currentBlock = super.XOR(Arrays.copyOfRange(plainText, i, i + 8), currentBlock);
							
			currentBlock = des.Encrypt(key1, key2, currentBlock);
			super.ReplaceBlock(cipherText, currentBlock, i);
		}
		
		return cipherText;
	}
	
	public byte[] Decrypt(SecretKey key1, SecretKey key2, byte[] IV, byte[] cipherText) throws Exception
	{
		if (IV.length != 8) 
			throw new Exception("CBC Decryption: Initalization Vector size =! 8");
	
		int cipherLength = cipherText.length;
		int r = cipherLength % 8;
		
		if (r != 0)
			throw new Exception("CBC Decryption: cipherText size is not a multiple of 8");
		
		byte[] plainText = new byte[cipherLength];
		byte[] currentBlock = new byte[8];
		
		TripleDES des = new TripleDES();
		
		for (int i = cipherLength - 8; i >= 0; i -= 8)
		{
			currentBlock = des.Decrypt(key1, key2, Arrays.copyOfRange(cipherText, i, i + 8));
			if (i == 0)
				currentBlock = super.XOR(currentBlock, IV);
			else
				currentBlock = super.XOR(Arrays.copyOfRange(cipherText, i - 8, i), currentBlock);	
			
			super.ReplaceBlock(plainText, currentBlock, i);	
		}
		
		return plainText;
	}
	
	public byte[] GetMac(byte[] key, byte[] IV, byte[] plainText) throws Exception
	{
		byte[] cipher = Encrypt(key, IV, plainText);
		return Arrays.copyOfRange(cipher, cipher.length - 8, cipher.length);
	}
	
	public boolean ValidateMac(byte[] key, byte[] IV, byte[] plainText, byte[] mac) throws Exception
	{
		byte[] myMac = GetMac(key, IV, plainText);
		//compare given mac with calculated mac
		if (mac.length == myMac.length)
		{
			for (int i = 0; i < myMac.length; i++)
			{
				if (mac[i] != myMac[i])
					return false;
			}
			
			return true;
		}
		else
			return false;
	}
}













